package cn.lijiajia3515.cairo.auth.modules.department;

import cn.hutool.core.util.ObjectUtil;
import cn.lijiajia3515.cairo.auth.domain.mongo.AuthMongoConstants;
import cn.lijiajia3515.cairo.auth.domain.mongo.DepartmentMongo;
import cn.lijiajia3515.cairo.auth.modules.AuthExtensionConstants;
import cn.lijiajia3515.cairo.core.CoreConstants;
import cn.lijiajia3515.cairo.core.page.Page;
import cn.lijiajia3515.cairo.core.tree.TreeConverter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 服务 - 部门
 */
@Slf4j
@Service
@Validated
public class DepartmentService {

	private final MongoTemplate mongoTemplate;

	public DepartmentService(MongoTemplate mongoTemplate) {
		this.mongoTemplate = mongoTemplate;
	}

	/**
	 * 部门 - 保存
	 *
	 * @param appId appId
	 * @param param  param
	 * @return 已保存的部门
	 */
	@Validated
	Department save(String appId, SaveDepartmentParam param) {
		final Criteria parentCriteria = Criteria
			.where(DepartmentMongo.FIELD.META.DELETED).is(AuthMongoConstants.Collection.DEPARTMENT)
			.and(DepartmentMongo.FIELD.ID).is(param.getParent());
		final Query ancestorsQuery = Query.query(parentCriteria);
		ancestorsQuery.fields().include(DepartmentMongo.FIELD.ANCESTORS);
		final List<String> ancestors = Optional.ofNullable(mongoTemplate.findOne(ancestorsQuery, DepartmentMongo.class, AuthMongoConstants.Collection.DEPARTMENT))
			.map(DepartmentMongo::getAncestors)
			.map(x -> Stream.of(x.stream(), Stream.of(param.getParent()))
				.flatMap(d -> d)
				.collect(Collectors.toList())
			)
			.orElse(Collections.singletonList(param.getParent()));


		DepartmentMongo mongo = DepartmentMongo.builder()
			.client(appId)
			.id(CoreConstants.SNOWFLAKE.nextIdStr())
			.parent(Optional.of(param.getParent()).orElse(DepartmentConstants.DEPARTMENT_TREE_ROOT))
			.ancestors(ancestors)
			.name(param.getName())
			.build();
		mongo.getMeta().setSort(CoreConstants.SNOWFLAKE.nextId());

		mongo = mongoTemplate.insert(mongo, AuthMongoConstants.Collection.DEPARTMENT);

		return DepartmentConverter.convert(mongo, DepartmentExtension.BASIC);
	}

	/**
	 * 部门 - 休息
	 *
	 * @param appId appId
	 * @param param  param
	 * @return 已修改的部门
	 */
	@Validated
	@Transactional(rollbackFor = Exception.class)
	public Optional<Department> modify(String appId, ModifyDepartmentParam param) {
		Query query = Query.query(
			Criteria
				.where(DepartmentMongo.FIELD.META.DELETED).is(AuthMongoConstants.NO_DELETED_VALUE)
				.and(DepartmentMongo.FIELD.CLIENT).is(appId)
				.and(DepartmentMongo.FIELD.ID).is(param.getId())
		);
		Update update = Update
			.update(DepartmentMongo.FIELD.NAME, param.getName())
			.set(DepartmentMongo.FIELD.REMARK, param.getRemark());
		final FindAndModifyOptions options = FindAndModifyOptions.options().returnNew(true);
		return mongoTemplate.update(DepartmentMongo.class)
			.inCollection(AuthMongoConstants.Collection.DEPARTMENT)
			.matching(query)
			.apply(update)
			.withOptions(options)
			.findAndModify()
			.map(x -> DepartmentConverter.convert(x, DepartmentExtension.ALL));
	}

	/**
	 * 部门删除
	 *
	 * @param appId appId
	 * @param param  部门
	 * @return 已删除 部门
	 */
	@Validated
	List<Department> delete(String appId, DeleteDepartmentParam param) {
		List<DepartmentMongo> content = mongoTemplate.findAllAndRemove(
			Query.query(Criteria
				.where(DepartmentMongo.FIELD.META.DELETED).is(AuthMongoConstants.NO_DELETED_VALUE)
				.and(DepartmentMongo.FIELD.CLIENT).is(appId)
				.and(DepartmentMongo.FIELD.ID).in(param.getIds())
			),
			DepartmentMongo.class,
			AuthMongoConstants.Collection.DEPARTMENT
		);
		return content.stream().map(x -> DepartmentConverter.convert(x, DepartmentExtension.ALL)).collect(Collectors.toList());
	}

	/**
	 * 查找
	 *
	 * @param appId appId
	 * @return 部门查询
	 */
	List<PathDepartment> findAncestorDepartment(@Valid @NotNull String appId, @Valid @Validated FindDepartmentParam param) {
		Criteria criteria = buildFindParam(appId, param);
		Query query = Query
			.query(criteria)
			.with(
				Sort.by(
					Sort.Order.asc(DepartmentMongo.FIELD.META.SORT),
					Sort.Order.asc(DepartmentMongo.FIELD.META.LAST_MODIFIED.AT),
					Sort.Order.asc(DepartmentMongo.FIELD.META.CREATED.AT),
					Sort.Order.asc(DepartmentMongo.FIELD.ID),
					Sort.Order.asc(DepartmentMongo.FIELD._ID)
				)
			);
		final List<DepartmentMongo> dms = mongoTemplate.find(query, DepartmentMongo.class, AuthMongoConstants.Collection.DEPARTMENT);
		return find(appId, dms);
	}

	/**
	 * 查找
	 *
	 * @param appId appId
	 * @return 部门查询
	 */
	Page<Department> findPage(@Valid @NotNull String appId, @Valid @Validated FindDepartmentParam param) {
		Criteria criteria = buildFindParam(appId, param);
		Query query = Query
			.query(criteria)
			.with(
				Sort.by(
					Sort.Order.asc(DepartmentMongo.FIELD.META.SORT),
					Sort.Order.asc(DepartmentMongo.FIELD.META.LAST_MODIFIED.AT),
					Sort.Order.asc(DepartmentMongo.FIELD.META.CREATED.AT),
					Sort.Order.asc(DepartmentMongo.FIELD.ID),
					Sort.Order.asc(DepartmentMongo.FIELD._ID)
				)
			);

		long total = mongoTemplate.count(query, DepartmentMongo.class, AuthMongoConstants.Collection.DEPARTMENT);

		query.with(param.pageable());
		query.with(
			Sort.by(
				Sort.Order.asc(DepartmentMongo.FIELD.META.SORT),
				Sort.Order.asc(DepartmentMongo.FIELD.META.LAST_MODIFIED.AT),
				Sort.Order.asc(DepartmentMongo.FIELD.META.CREATED.AT),
				Sort.Order.asc(DepartmentMongo.FIELD.ID),
				Sort.Order.asc(DepartmentMongo.FIELD._ID)
			)
		);
		List<Department> ds = find(mongoTemplate.find(query, DepartmentMongo.class, AuthMongoConstants.Collection.DEPARTMENT), param.getExtension());

		return new Page<>(param, ds, total);
	}

	/**
	 * 属性 查询
	 *
	 * @param appId appId
	 * @return 部门 list
	 */
	List<DepartmentTreeNode> findTree(@NotNull String appId, FindTreeDepartmentParam param) {
		final Criteria criteria = Criteria
			.where(DepartmentMongo.FIELD.META.DELETED).is(AuthMongoConstants.NO_DELETED_VALUE)
			.and(DepartmentMongo.FIELD.CLIENT).is(appId);
		Optional.ofNullable(param.getParent())
			.filter(x -> !ObjectUtil.equal(DepartmentConstants.DEPARTMENT_TREE_ROOT, x))
			.ifPresent(x -> criteria.and(DepartmentMongo.FIELD.ANCESTORS).elemMatch(new Criteria().is(x)));
		final Query query = Query.query(Criteria.where(DepartmentMongo.FIELD.CLIENT).is(appId));
		List<DepartmentMongo> ms = mongoTemplate.find(query, DepartmentMongo.class, AuthMongoConstants.Collection.DEPARTMENT);
		List<DepartmentTreeNode> nodes = ms.stream().map(DepartmentConverter::treeConvert).collect(Collectors.toList());
		return TreeConverter.build(nodes, DepartmentConstants.DEPARTMENT_TREE_ROOT, DepartmentConstants.TREE_COMPARATOR);
	}

	Optional<PathDepartment> findId(@NotNull String appId, @Valid @NotNull String id) {
		Criteria lastCriteria = Criteria
			.where(DepartmentMongo.FIELD.META.DELETED).is(AuthMongoConstants.NO_DELETED_VALUE)
			.and(DepartmentMongo.FIELD.CLIENT).is(appId)
			.and(DepartmentMongo.FIELD.ID).is(id);

		Query lastQuery = Query.query(lastCriteria);
		return Optional.ofNullable(mongoTemplate.findOne(lastQuery, DepartmentMongo.class, AuthMongoConstants.Collection.DEPARTMENT))
			.flatMap(last -> find(appId, Collections.singletonList(last)).stream().findFirst());
	}

	List<PathDepartment> find(String appId, List<DepartmentMongo> lastDms) {
		final Set<String> ancestors = lastDms.stream()
			.flatMap(d -> d.getAncestors().stream())
			.collect(Collectors.toSet());

		Criteria ancestorsCriteria = Criteria
			.where(DepartmentMongo.FIELD.META.DELETED).is(AuthMongoConstants.NO_DELETED_VALUE)
			.and(DepartmentMongo.FIELD.CLIENT).is(appId)
			.and(DepartmentMongo.FIELD.ID).in(ancestors);

		Query ancestorsQuery = Query.query(ancestorsCriteria);
		final Map<String, DepartmentMongo> ancestorDepartmentMap = mongoTemplate.find(ancestorsQuery, DepartmentMongo.class, AuthMongoConstants.Collection.DEPARTMENT)
			.stream()
			.collect(Collectors.toMap(DepartmentMongo::getId, x -> x));

		return lastDms.stream()
			.map(d -> {
				final List<String> subAncestors = Optional.ofNullable(d.getAncestors()).orElse(Collections.emptyList());
				final List<DepartmentMongo> ancestorDms = subAncestors.stream()
					.map(id -> ancestorDepartmentMap.getOrDefault(id, DepartmentMongo.builder().id(id).name(id).remark("缺省数据").build()))
					.collect(Collectors.toList());
				final List<DepartmentMongo> allDms = Stream.of(ancestorDms.stream(), Stream.of(d)).flatMap(x -> x).collect(Collectors.toList());

				return PathDepartment.builder()
					.ids(allDms.stream().map(DepartmentMongo::getId).collect(Collectors.toList()))
					.names(allDms.stream().map(DepartmentMongo::getName).collect(Collectors.toList()))
					.remarks(allDms.stream().map(DepartmentMongo::getRemark).collect(Collectors.toList()))
					.build();
			})
			.collect(Collectors.toList());
	}

	List<Department> find(@NotNull String appId, @Valid @Validated FindDepartmentParam param) {
		Criteria criteria = buildFindParam(appId, param);
		Query query = Query
			.query(criteria)
			.with(
				Sort.by(
					Sort.Order.asc(DepartmentMongo.FIELD.META.SORT),
					Sort.Order.asc(DepartmentMongo.FIELD.META.LAST_MODIFIED.AT),
					Sort.Order.asc(DepartmentMongo.FIELD.META.CREATED.AT),
					Sort.Order.asc(DepartmentMongo.FIELD.ID),
					Sort.Order.asc(DepartmentMongo.FIELD._ID)
				)
			);

		List<DepartmentMongo> dms = mongoTemplate.find(query, DepartmentMongo.class, AuthMongoConstants.Collection.DEPARTMENT);
		return find(dms, param.getExtension());
	}

	Criteria buildFindParam(@NotNull String appId, @NotNull FindDepartmentParam param) {
		Criteria criteria = Criteria
			.where(DepartmentMongo.FIELD.META.DELETED).is(AuthMongoConstants.NO_DELETED_VALUE)
			.and(DepartmentMongo.FIELD.CLIENT).is(appId);
		Optional.ofNullable(param.getIds()).ifPresent(ids -> criteria.and(DepartmentMongo.FIELD.ID).in(ids));
		Optional.ofNullable(param.getParent()).ifPresent(parent -> criteria.and(DepartmentMongo.FIELD.PARENT).is(parent));
		return criteria;
	}

	List<Department> find(List<DepartmentMongo> ms, Map<String, String> extensionMap) {
		final DepartmentExtension extension = Optional.of(extensionMap.getOrDefault(AuthExtensionConstants.DEPARTMENT, DepartmentExtension.ALL.name()))
			.map(DepartmentExtension::valueOf)
			.orElse(DepartmentExtension.ALL);
		return ms.stream().map(x -> DepartmentConverter.convert(x, extension)).collect(Collectors.toList());
	}

}
